﻿using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Web.UI;
using System.Collections.Generic;

namespace HeroldIT.Dnn.Modules
{
    /// <summary>
    /// Represents a hierarchical collection of objects.
    /// </summary>
    /// <typeparam name="T">The type enclosed in the hierarchy.</typeparam>
    public class HierarchyObjectCollection<T> : Collection<HierarchyObject<T>>, IHierarchicalEnumerable
    {
        #region properties

        /// <summary>
        /// Gets the object owning this <see cref="HierarchyObjectCollection{T}"/>.
        /// </summary>
        public HierarchyObject<T> Owner { get; private set; }

        /// <summary>
        /// Gets or sets the string used for separating path parts.
        /// </summary>
        protected string PathSeparator { get; set; }

        #endregion

        #region events

        /// <summary>
        /// Fired, when a new object needs to be created due to an <see cref="Add"/> operation.
        /// </summary>
        public event EventHandler<HierarchyObjectCreateEventArgs<T>> CreateObject;

        /// <summary>
        /// Fires the <see cref="CreateObject"/> event.
        /// </summary>
        /// <param name="e">The <see cref="HierarchyObjectCreateEventArgs{T}"/> to pass to the event.</param>
        protected void OnCreateObject(HierarchyObjectCreateEventArgs<T> e)
        {
            EventHandler<HierarchyObjectCreateEventArgs<T>> handler = this.CreateObject;
            if (handler != null)
                handler(this, e);
            else
            {
                if (typeof(T) == typeof(string))
                {
                    e.Object = (T)(object)e.ObjectString;
                }
            }
        }

        #endregion

        #region constructors

        /// <summary>
        /// Creates a new root level <see cref="HierarchyObjectCollection{T}"/>.
        /// </summary>
        public HierarchyObjectCollection() : this(null)
        {}

        /// <summary>
        /// Creates a new child <see cref="HierarchyObjectCollection{T}"/>.
        /// </summary>
        /// <param name="owner"></param>
        internal HierarchyObjectCollection(HierarchyObject<T> owner)
        {
            this.PathSeparator = ">";
            this.Owner = owner;
        }

        #endregion

        #region methods

        /// <summary>
        /// Gets an enumerable allowing you to enumerate the complete (sub-)hierarchy.
        /// </summary>
        /// <returns>An enumerable allowing you to enumerate the complete (sub-)hierarchy.</returns>
        public IEnumerable<HierarchyObject<T>> EnumerateTree()
        {
            return new HierarchyTreeEnumerable(this);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="objPath"></param>
        /// <returns></returns>
        public bool Contains(string objPath)
        {
            if (objPath == null)
                throw new ArgumentNullException("objPath");

            return null != this.Find(objPath);
        }

        private HierarchyObject<T> FindLocal(string objStr)
        {
            foreach (HierarchyObject<T> item in this)
            {
                if (item.Object.ToString().Equals(objStr))
                {
                    return item;
                }
            }
            return null;
        }

        /// <summary>
        /// Looks for the object specified by the path.
        /// </summary>
        /// <param name="objPath">The path of the object to find.</param>
        /// <returns>The object if found, otherwise <c>null</c>.</returns>
        public HierarchyObject<T> Find(string objPath)
        {
            if (objPath == null)
                throw new ArgumentNullException("objPath");

            string[] pathParts = objPath.Split(new[] { this.PathSeparator }, 2, StringSplitOptions.None);
            HierarchyObject<T> result = this.FindLocal(pathParts[0]);
            if (null == result)
                return null;

            if (pathParts.Length > 1)
            {
                return result.Children.Find(pathParts[1]);
            }

            return result;
        }

        /// <summary>
        /// Adds an object to the hierarchy at the specified path.
        /// </summary>
        /// <param name="objPath">The path of the object to add.</param>
        public void Add(string objPath)
        {
            if (objPath == null)
                throw new ArgumentNullException("objPath");

            string[] pathParts = objPath.Split(new[] { this.PathSeparator }, 2, StringSplitOptions.None);
            string objStr = pathParts[0];
            HierarchyObject<T> result = this.FindLocal(objStr);
            if (null == result)
            {
                HierarchyObjectCreateEventArgs<T> e = new HierarchyObjectCreateEventArgs<T>(objStr);
                this.OnCreateObject(e);
                if (!e.ObjectSet)
                    throw new InvalidOperationException("Object for hierarchy was not created.");

                result = new HierarchyObject<T>(this.Owner, e.Object);
                if (null == this.Owner)
                {
                    this.Add(result);
                }
            }
            if (pathParts.Length > 1)
            {
                result.Children.Add(pathParts[1]);
            }
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region Implementation of IHierarchicalEnumerable

        /// <summary>
        /// Returns a hierarchical data item for the specified enumerated item.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Web.UI.IHierarchyData"/> instance that represents the <see cref="T:System.Object"/> passed to the <see cref="M:System.Web.UI.IHierarchicalEnumerable.GetHierarchyData(System.Object)"/> method.
        /// </returns>
        /// <param name="enumeratedItem">The <see cref="T:System.Object"/> for which to return an <see cref="T:System.Web.UI.IHierarchyData"/>. 
        ///                 </param>
        public IHierarchyData GetHierarchyData(object enumeratedItem)
        {
            return enumeratedItem as IHierarchyData;
        }

        #endregion

        #region child classes

        private class HierarchyTreeEnumerable : IEnumerable<HierarchyObject<T>>
        {
            #region fields

            private readonly HierarchyObjectCollection<T> owner;

            #endregion

            #region constructors

            public HierarchyTreeEnumerable(HierarchyObjectCollection<T> owner)
            {
                this.owner = owner;
            }

            #endregion

            #region Implementation of IEnumerable<HierarchyObject<T>>

            /// <summary>
            /// Returns an enumerator that iterates through a collection.
            /// </summary>
            /// <returns>
            /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
            /// </returns>
            /// <filterpriority>2</filterpriority>
            public IEnumerator<HierarchyObject<T>> GetEnumerator()
            {
                foreach (HierarchyObject<T> obj in this.owner.Items)
                {
                    yield return obj;
                    if (obj.HasChildren)
                    {
                        foreach (HierarchyObject<T> child in obj.Children.EnumerateTree())
                        {
                            yield return child;
                        }
                    }
                }
            }

            #endregion

            #region Implementation of IEnumerable

            /// <summary>
            /// Returns an enumerator that iterates through a collection.
            /// </summary>
            /// <returns>
            /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
            /// </returns>
            /// <filterpriority>2</filterpriority>
            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }

            #endregion
        }

        #endregion
    }
}
